// 定义 pathchecker 包，用于路径检查相关功能
package pathchecker

// 导入所需的标准库包
import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// 路径类型枚举，用于表示不同的路径类型
type PathType int

// 定义各种路径类型常量
const (
	TypeUnknown   PathType = iota // 未知类型
	TypeFile                      // 文件
	TypeDir                       // 目录
	TypeSymlink                   // 符号链接
	TypeDevice                    // 设备文件
	TypeSocket                    // 套接字文件
	TypeNamedPipe                 // 命名管道
)

// PathInfo 结构体，用于封装路径的各种信息
type PathInfo struct {
	Path      string      // 路径
	Exists    bool        // 路径是否存在
	Type      PathType    // 路径类型
	Size      int64       // 文件大小，单位为字节
	Mode      os.FileMode // 文件权限
	ModTime   time.Time   // 文件最后修改时间
	IsDir     bool        // 是否为目录
	IsSymlink bool        // 是否为符号链接
	RealPath  string      // 符号链接的目标路径，如果是符号链接的话
}

// 自定义错误类型，用于处理路径相关的错误
type PathError struct {
	Path string // 出错的路径
	Err  error  // 具体的错误信息
	Code int    // 错误码
}

// 实现 error 接口，返回格式化后的错误信息
func (e *PathError) Error() string {
	return fmt.Sprintf("path error (code=%d): %s: %v", e.Code, e.Path, e.Err)
}

// 定义路径检查相关的错误码常量
const (
	ErrNotExist         = iota + 1 // 路径不存在
	ErrPermissionDenied            // 权限不足
	ErrInvalidPath                 // 无效路径
	ErrUnknown                     // 未知错误
)

// 用于缓存路径信息的并发安全映射
var pathInfoCache = sync.Map{}

// CheckPath 函数，用于检查给定路径的详细信息
func CheckPath(path string) (PathInfo, error) {
	// 初始化 PathInfo 结构体，填充默认值
	info := newPathInfo(path)

	// 检查路径是否为空，如果为空则返回错误
	if path == "" {
		return info, &PathError{
			Path: path,
			Err:  errors.New("path is empty"),
			Code: ErrInvalidPath,
		}
	}

	// 使用 os.Lstat 获取文件信息，该函数不会跟随符号链接
	fileInfo, err := os.Lstat(path)
	if err != nil {
		// 处理路径不存在的情况
		if os.IsNotExist(err) {
			return info, &PathError{
				Path: path,
				Err:  err,
				Code: ErrNotExist,
			}
		} else if os.IsPermission(err) { // 处理权限不足的情况
			return info, &PathError{
				Path: path,
				Err:  err,
				Code: ErrPermissionDenied,
			}
		}
		// 处理其他未知错误
		return info, &PathError{
			Path: path,
			Err:  err,
			Code: ErrUnknown,
		}
	}

	// 路径存在，填充基本信息到 PathInfo 结构体
	info.Exists = true
	info.Mode = fileInfo.Mode()
	info.Type, info.IsDir = parseFileMode(info.Mode)

	// 如果是符号链接，处理符号链接相关信息
	if info.Type == TypeSymlink {
		info.IsSymlink = true
		// 解析符号链接，获取目标路径
		realPath, err := os.Readlink(path)
		if err == nil {
			info.RealPath = realPath
		}
	}

	// 如果是普通文件或目录，获取实际的文件信息
	if info.Type == TypeFile || info.Type == TypeDir {
		// 使用 os.Stat 获取文件信息，会跟随符号链接
		realInfo, err := os.Stat(path)
		if err == nil {
			info.Size = realInfo.Size()
			info.ModTime = realInfo.ModTime()
		}
	}

	return info, nil
}

// CheckPathCached 函数，带缓存的路径检查功能
func CheckPathCached(path string) (PathInfo, error) {
	// 检查缓存中是否存在该路径的信息
	if cached, ok := pathInfoCache.Load(path); ok {
		return cached.(PathInfo), nil
	}

	// 缓存中不存在，执行实际的路径检查
	info, err := CheckPath(path)
	if err == nil {
		// 检查成功，将结果存入缓存
		pathInfoCache.Store(path, info)
		// 设置 5 分钟后自动从缓存中删除该信息
		time.AfterFunc(5*time.Minute, func() {
			pathInfoCache.Delete(path)
		})
	}

	return info, err
}

// newPathInfo 函数，用于初始化一个 PathInfo 结构体的零值
func newPathInfo(path string) PathInfo {
	return PathInfo{
		Path:    filepath.Clean(path), // 清理路径格式
		Exists:  false,                // 默认路径不存在
		Type:    TypeUnknown,          // 默认路径类型未知
		Size:    0,                    // 默认文件大小为 0
		ModTime: time.Time{},          // 默认修改时间为零值
	}
}

// parseFileMode 函数，根据文件模式解析路径类型和是否为目录
func parseFileMode(mode os.FileMode) (PathType, bool) {
	switch {
	case mode.IsDir():
		return TypeDir, true // 目录类型
	case mode.IsRegular():
		return TypeFile, false // 普通文件类型
	case mode&os.ModeSymlink != 0:
		return TypeSymlink, false // 符号链接类型
	case mode&os.ModeDevice != 0:
		return TypeDevice, false // 设备文件类型
	case mode&os.ModeSocket != 0:
		return TypeSocket, false // 套接字文件类型
	case mode&os.ModeNamedPipe != 0:
		return TypeNamedPipe, false // 命名管道类型
	default:
		return TypeUnknown, false // 未知类型
	}
}

// CheckAndCreateDir 函数，检查指定目录是否存在，若不存在且 create 为 true 则创建该目录
// 参数 dir 为要检查或创建的目录路径
// 参数 create 指示在目录不存在时是否创建该目录
// 返回值为错误对象，若操作过程中出错则返回相应错误信息
func CheckAndCreateDir(dir string, create bool) error {
	// 检查目录状态
	info, err := os.Stat(dir)

	// 处理目录不存在的情况
	if os.IsNotExist(err) {
		if !create {
			return fmt.Errorf("目录不存在: %s", dir)
		}

		// 创建目录，使用 0755 权限
		if mkdirErr := os.MkdirAll(dir, 0755); mkdirErr != nil {
			return fmt.Errorf("无法创建目录 %s: %w", dir, mkdirErr)
		}

		// 验证目录是否创建成功
		if _, statErr := os.Stat(dir); os.IsNotExist(statErr) {
			return fmt.Errorf("目录创建成功但无法访问: %s", dir)
		}
		return nil
	}

	// 处理其他错误
	if err != nil {
		return fmt.Errorf("目录状态检查失败: %w", err)
	}

	// 处理路径存在但不是目录的情况
	if !info.IsDir() {
		return fmt.Errorf("路径存在但不是目录: %s", dir)
	}

	// 检查并修改目录权限
	if create && info.Mode().Perm() != 0755 {
		if err := os.Chmod(dir, 0755); err != nil {
			return fmt.Errorf("无法修改目录权限: %w", err)
		}
	}

	return nil
}
